package com.huawei.admins.platform.gateway.core.server.gateway;

import com.huawei.admins.platform.gateway.core.component.filter.GatewayFilter;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.codec.ServerCodecConfigurer;
import org.springframework.util.Assert;
import org.springframework.web.server.WebExceptionHandler;
import org.springframework.web.server.WebHandler;
import org.springframework.web.server.handler.ExceptionHandlingWebHandler;
import org.springframework.web.server.i18n.LocaleContextResolver;
import org.springframework.web.server.session.DefaultWebSessionManager;
import org.springframework.web.server.session.WebSessionManager;

import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;

public class GatewayHttpHandlerBuilder {

    /** Well-known name for the target WebHandler in the bean factory. */
    public static final String WEB_HANDLER_BEAN_NAME = "gatewayWebHandler";

    /** Well-known name for the WebSessionManager in the bean factory. */
    public static final String WEB_SESSION_MANAGER_BEAN_NAME = "webSessionManager";

    /** Well-known name for the ServerCodecConfigurer in the bean factory. */
    public static final String SERVER_CODEC_CONFIGURER_BEAN_NAME = "serverCodecConfigurer";

    /** Well-known name for the LocaleContextResolver in the bean factory. */
    public static final String LOCALE_CONTEXT_RESOLVER_BEAN_NAME = "localeContextResolver";

    /** Well-known name for the ForwardedHeaderTransformer in the bean factory. */
    public static final String FORWARDED_HEADER_TRANSFORMER_BEAN_NAME = "forwardedHeaderTransformer";

    private final ApplicationContext applicationContext;

    private final GatewayWebHandler webHandler;

    private final List<GatewayFilter> filters;

    private final List<WebExceptionHandler> exceptionHandlers;

    private WebSessionManager sessionManager;

    private ServerCodecConfigurer codecConfigurer;

    private LocaleContextResolver localeContextResolver;

    public GatewayHttpHandlerBuilder(GatewayWebHandler webHandler, @NotNull ApplicationContext applicationContext) {
        Assert.notNull(webHandler, "webHandler must not be null");
        Assert.notNull(applicationContext, "applicationContext must not be null");
        this.applicationContext = applicationContext;
        this.webHandler = webHandler;
        this.filters = new ArrayList<>();
        this.exceptionHandlers = new ArrayList<>();
        this.sessionManager = new DefaultWebSessionManager();
    }

    public static GatewayHttpHandlerBuilder applicationContext(ApplicationContext context) {

        GatewayHttpHandlerBuilder builder = new GatewayHttpHandlerBuilder(
                context.getBean(WEB_HANDLER_BEAN_NAME, GatewayWebHandler.class), context);

        List<GatewayFilter> gatewayFilters = context
                .getBeanProvider(GatewayFilter.class)
                .orderedStream()
                .toList();
        builder.filters(filters -> filters.addAll(gatewayFilters));

        List<WebExceptionHandler> exceptionHandlers = context
                .getBeanProvider(WebExceptionHandler.class)
                .orderedStream()
                .toList();
        builder.exceptionHandlers(handlers -> handlers.addAll(exceptionHandlers));

        try {
            builder.sessionManager(context.getBean(WEB_SESSION_MANAGER_BEAN_NAME, WebSessionManager.class));
        } catch (NoSuchBeanDefinitionException ex) {
            // Fall back on default
        }
        try {
            builder.codecConfigurer(context.getBean(SERVER_CODEC_CONFIGURER_BEAN_NAME, ServerCodecConfigurer.class));
        } catch (NoSuchBeanDefinitionException ex) {
            // Fall back on default
        }

        try {
            builder.localeContextResolver(context.getBean(LOCALE_CONTEXT_RESOLVER_BEAN_NAME, LocaleContextResolver.class));
        } catch (NoSuchBeanDefinitionException ex) {
            // Fall back on default
        }
        return builder;
    }

    /**
     * Manipulate the "live" list of currently configured filters.
     * @param consumer the consumer to use
     */
    public GatewayHttpHandlerBuilder filters(Consumer<List<GatewayFilter>> consumer) {
        consumer.accept(this.filters);
        return this;
    }

    /**
     * Add the given exception handler(s).
     * @param consumer the exception handler(s)
     */
    public GatewayHttpHandlerBuilder exceptionHandlers(Consumer<List<WebExceptionHandler>> consumer) {
        consumer.accept(this.exceptionHandlers);
        return this;
    }

    public GatewayHttpHandlerBuilder sessionManager(WebSessionManager manager) {
        this.sessionManager = manager;
        return this;
    }

    public GatewayHttpHandlerBuilder codecConfigurer(ServerCodecConfigurer codecConfigurer) {
        this.codecConfigurer = codecConfigurer;
        return this;
    }

    public GatewayHttpHandlerBuilder localeContextResolver(LocaleContextResolver localeContextResolver) {
        this.localeContextResolver = localeContextResolver;
        return this;
    }

    public GatewayHttpHandler build() {

        WebHandler decorated = new ExceptionHandlingWebHandler(this.webHandler, this.exceptionHandlers);
        GatewayHttpHandler adapted = new GatewayHttpHandler(decorated);

        if (this.sessionManager != null) {
            adapted.setSessionManager(this.sessionManager);
        }
        if (this.codecConfigurer != null) {
            adapted.setCodecConfigurer(this.codecConfigurer);
        }
        if (this.localeContextResolver != null) {
            adapted.setLocaleContextResolver(this.localeContextResolver);
        }
        if (this.applicationContext != null) {
            adapted.setApplicationContext(this.applicationContext);
        }
        adapted.afterPropertiesSet();

        return adapted;
    }


}
